import minimist from 'minimist'
import path from 'path'
import prompts from 'prompts'
import consola from 'consola'
import fs from 'node:fs'
import {
  greenBright,
  blue,
  blueBright,
  cyan,
  cyanBright,
  red,
  reset,
  yellow,
  yellowBright,
} from 'colorette'
import { fileURLToPath } from 'url'

consola.log(greenBright('---[start]---'))

const argv = minimist(process.argv.slice(2), { string: ['_'] })
const cwd = process.cwd()
consola.log(yellow(cwd))

consola.log(blueBright('--[argv]--：'))
console.log(argv)

const frameworks = [
  {
    name: 'vue',
    display: 'Vue',
    color: yellowBright,
    variants:  [
      {
        name: 'vue-ts',
        display: 'TypeScript',
        color: cyanBright,
      },
      {
        name: 'vue',
        display: 'JavaScript',
        color: cyan,
      },
    ]
  }
]

const templates = frameworks.flatMap(f => f.variants.map(v => v.name))

const renameFiles = {
  _gitignore: '.gitignore',
}

const defaultTargetDir = 'vite-project'

const init = async () => {
  const argTargetDir = formatTargetDir(argv._[0])
  const argTemplate = argv.template || argv.t

  let targetDir = argTargetDir || defaultTargetDir
  const getProjectName = () => {
    return targetDir === '.' ? path.basename(path.resolve()) : targetDir
  }

  let result

  try {
    result = await prompts(
      [
        // 项目名
        {
          // type = fasly值 会跳过
          type: argTargetDir ? null : 'text',
          name: 'projectName',
          message: reset('Project name:'),
          initial: defaultTargetDir,
          onState: (state) => {
            targetDir = formatTargetDir(state.value) || defaultTargetDir
          }
        },
        {
          type: () => !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm',
          name: 'overwrite',
          message: () => (
            targetDir === '.'
              ? 'Current directory'
              : `Target directory "${targetDir}"`
          ) + ' is not empty. Remove existing files and continue?'
        },
        {
          type: (_, { overwrite }: { overwrite?: boolean }) => {
            if (overwrite === false) {
              throw new Error(red('✖') + ' Operation cancelled')
            }
            return null
          },
          name: 'overwriteChecker',
        },
        {
          type: () => (isValidPackageName(getProjectName()) ? null : 'text'),
          name: 'packageName',
          message: reset('Package name:'),
          initial: () => toValidPackageName(getProjectName()),
          validate: (dir) => isValidPackageName(dir) || 'Invalid package.json name'
        },
        // 选择框架
        {
          type: argTemplate && templates.includes(argTemplate) ? null : 'select',
          name: 'framework',
          message: typeof argTemplate === 'string' && !templates.includes(argTemplate)
            ? reset(
                `"${argTemplate}" isn't a valid template. Please choose from below:`
              )
            : reset('Select a framework:'),
          initial: 0,
          choices: frameworks.map(framework => {
            const frameworkColor = framework.color
            return {
              title: frameworkColor(framework.display || framework.name),
              value: framework
            }
          })
        },
        // 选择框架下预设的项目
        {
          type: (framework) => framework && framework.variants ? 'select' : null,
          name: 'variants',
          message: reset('Select a variants:'),
          choices: (framework) => framework.variants.map(variant => {
            const variantColor = variant.color
            return {
              title: variantColor(variant.display || variant.name),
              value: variant.name
            }
          })
        },
        {
          onCancel: () => {
            throw new Error(red('✖') + ' Operation cancelled')
          }
        }
      ]
    )

    // console.log('--[result]--', result)
  } catch(cancelled) {
    consola.log(red(cancelled.message))
    return
  }

  const { framework, overwrite, packageName, variant } = result

  // 项目根目录
  const root = path.join(cwd, targetDir)

  // 处理路径文件夹
  if (overwrite) {
    emptyDir(root)
  } else if (!fs.existsSync(root)) {
    fs.mkdirSync(root, { recursive: true })
  }

  let template = variant || framework?.name || argTemplate
  let isReactSwc = false
  if (template.includes('-swc')) {
    isReactSwc = true
    template = template.replace('-swc', '')
  }

  const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent)
  const pkgManager = pkgInfo ? pkgInfo.name : 'pnpm'
  const isYarn1 = pkgManager === 'yarn' && pkgInfo?.version.startsWith('1.')

  consola.log(blue(`\nScaffolding project in ${root}...`))

  // 模板路径
  const templateDir = path.resolve(
    fileURLToPath(import.meta.url),
    '../..',
    `template-${template}`
  )

  // console.log('--templatedir--', templateDir)

  // 往项目根目录写文件
  const write = (file: string, content?: string) => {
    const targetPath = path.join(root, renameFiles[file] ?? file)
    if (content) {
      fs.writeFileSync(targetPath, content)
    } else {
      copy(path.join(templateDir, file), targetPath)
    }
  }

  const files = fs.readdirSync(templateDir)
  for(const file of files.filter(f => f !== 'package.json')) {
    write(file)
  }

  // package.json
  const pkg = JSON.parse(
    fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8')
  )

  pkg.name = packageName || getProjectName()

  write('package.json', JSON.stringify(pkg, null, 2) + '\n')

  const cdProjectName = path.relative(cwd, root)
  consola.log(greenBright(`\nDone. Now run :\n`))
  if (root !== cwd) {
    consola.log(blueBright(
      ` cd ${
        cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName
      }`
    ))
  }
  switch(pkgManager) {
    case 'yarn':
      consola.log(blue('  yarn'))
      consola.log(blue('  yarn dev'))
      break
    default:
      consola.log(blue(` ${pkgManager} install`))
      consola.log(blue(` ${pkgManager} run dev`))
      break
  }
}

// 去除空格，并去除末尾多余的 /
function formatTargetDir(targetDir: string | undefined) {
  return targetDir?.trim().replace(/\/+$/g, '')
}
// 是空文件夹 或者 只有.git 文件夹
function isEmpty(path: string) {
  const files = fs.readdirSync(path)
  return files.length === 0 || (files.length === 1 && files[0] === '.git')
}
function isValidPackageName(projectName: string) {
  return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(
    projectName,
  )
}
function toValidPackageName(projectName: string) {
  return projectName
    .trim()
    .toLowerCase()
    .replace(/\s+/g, '-')
    .replace(/^[._]/, '')
    .replace(/[^a-z\d\-~]+/g, '-')
}
// 清空文件夹
function emptyDir(dir: string) {
  if (!fs.existsSync(dir)) {
    return
  }
  for(const file of fs.readdirSync(dir)) {
    if (file === '.git') {
      continue
    }
    fs.rmSync(path.resolve(dir, file), { recursive: true, force: true })
  }
}

function pkgFromUserAgent(userAgent: string | undefined) {
  if (!userAgent) return undefined
  const pkgSpec = userAgent.split(' ')[0]
  const pkgSpecArr = pkgSpec.split('/')
  return {
    name: pkgSpecArr[0],
    version: pkgSpecArr[1],
  }
}

function copy(src: string, dest: string) {
  const stat = fs.statSync(src)
  if (stat.isDirectory()) {
    copyDir(src, dest)
  } else {
    fs.copyFileSync(src, dest)
  }
}
function copyDir(srcDir: string, destDir: string) {
  fs.mkdirSync(destDir, { recursive: true })
  for (const file of fs.readdirSync(srcDir)) {
    const srcFile = path.resolve(srcDir, file)
    const destFile = path.resolve(destDir, file)
    copy(srcFile, destFile)
  }
}
// 修改文件
function editFile(file: string, callback: (content: string) => string) {
  const content = fs.readFileSync(file, 'utf-8')
  fs.writeFileSync(file, callback(content), 'utf-8')
}

init().catch(e => {
  consola.log(red(e))
})
